﻿using System;
using System.IO;
using System.Data;
using System.Collections.Generic;

//自定义命名空间
using DreamCube.Foundation.Basic.Extensions.System.StringIO;
using DreamCube.Foundation.Basic.Objects.EqualityComparers;

namespace PicServer.BLL
{
    /// <summary>
    /// 提供文件的存储和读取的功能
    /// </summary>
    public static class FileService
    {
        #region "字段"

        /// <summary>
        /// 读取文件的流缓冲区
        /// </summary>
        private static Dictionary<String, FileStream> readFileStreamCache = new Dictionary<String, FileStream>();

        /// <summary>
        /// 状态
        /// </summary>
        private static Model.ServerState state = Model.ServerState.Stop;

        /// <summary>
        /// 目录路径(最后包含有斜杠符号\)
        /// </summary>
        private static String folderPath;

        /// <summary>
        /// 固定的，表前缀
        /// </summary>
        private static String tablePrefix = "FileTable";

        /// <summary>
        /// 文件流的缓存区
        /// </summary>
        private static Dictionary<String, FileStream> writeFsCache = null;

        /// <summary>
        /// 文件序号的缓冲区
        /// </summary>
        private static Dictionary<String, Int32> fileIndexCache = null;

        /// <summary>
        /// 文件的最大容量
        /// </summary>
        private static Int64 fileMaxSize;

        /// <summary>
        /// 单个文件的最大长度
        /// </summary>
        private static Int32 perFileMaxSize;

        #endregion

        #region "属性"

        /// <summary>
        /// 返回当前服务的状态
        /// </summary>
        public static Model.ServerState ServerState
        {
            get { return FileService.state; }
        }

        #endregion

        #region "公共方法"

        /// <summary>
        /// 停止服务
        /// </summary>
        public static void Stop()
        {
            FileService.state = Model.ServerState.Stop;
        }

        /// <summary>
        /// 启动文件服务，必须传入目录
        /// </summary>
        /// <param name="folderPath">文件夹路径</param>
        /// <param name="fileMaxSize">大文件的最大大小</param>
        /// <param name="perFileMaxSize">单个文件的最大大小</param>
        /// <param name="startType">服务器启动类型</param>
        /// <returns></returns>
        public static void Start(String folderPath, Int64 fileMaxSize, Int32 perFileMaxSize, Model.ServiceStartType startType)
        {
            //先确保目录存在
            folderPath.EnsurePath();
            if (!folderPath.EndsWith("\\")) folderPath += "\\";
            FileService.folderPath = folderPath;
            //文件的大小配置
            FileService.fileMaxSize = fileMaxSize;
            FileService.perFileMaxSize = perFileMaxSize;

            //如果启动方式是只读的，则不需要缓存其他数据
            if (startType != Model.ServiceStartType.GetOnly)
            {
                //打开所有待写的文件流
                writeFsCache = new Dictionary<String, FileStream>(new StringEqualityComparerGeneric());
                fileIndexCache = new Dictionary<String, Int32>(new StringEqualityComparerGeneric());
                DataTable data = DAL.FileTableFileIndex.GetAllFileIndexData();
                if (data != null)
                {
                    for (Int32 i = 0, j = data.Rows.Count; i < j; i++)
                    {
                        String tableName = Convert.ToString(data.Rows[i]["tableName"]);
                        Int32 index = Convert.ToInt32(data.Rows[i]["fileIndex"]);
                        writeFsCache.Add(tableName, GetWriteFs(GetFilePath(folderPath, tableName, index)));
                        fileIndexCache.Add(tableName, index);
                    }
                }
            }
            state = Model.ServerState.Running;
        }

        /// <summary>
        /// 根据文件的索引读取文件
        /// </summary>
        /// <param name="fileUnicode"></param>
        /// <returns></returns>
        public static Byte[] ReadFile(Int64 fileUnicode)
        {
            FileIndex index = new FileIndex(fileUnicode);
            if (index.TableIndex < 0 || index.TableIndex > 255)
                return null;
            Model.FileIndexData fileRecord =
                    DAL.FileTable.GetFileRecord(tablePrefix + index.TableIndex, index.RecordIndex);
            if (fileRecord == null) return null;
            String filePath = folderPath + fileRecord.FileName;
            Byte[] fileData = new Byte[fileRecord.FileDataLength];
            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                fs.Seek(fileRecord.FileStartIndex, SeekOrigin.Begin);
                fs.Read(fileData, 0, fileRecord.FileDataLength);
            }
            return fileData;
        }

        /// <summary>
        /// 保存文件数据
        /// </summary>
        /// <param name="fileData"></param>
        /// <param name="fileDes">对文件的描述信息</param>
        /// <param name="alwaysFlush">在一些需要实时查询的模块中，需要每次都把缓冲区数据写入到磁盘中，默认值为true，每次都写入到磁盘中</param>
        /// <returns></returns>
        public static Int64 SaveFile(Byte[] fileData, String fileDes = "", Boolean alwaysFlush = true)
        {
            //判断文件的大小
            if (fileData.Length > FileService.perFileMaxSize)
                throw new ArgumentException(
                    String.Format(Properties.Resources.ExceptionOverflowFileSize, FileService.perFileMaxSize), "fileData");
            //通过循环获取一个数据库表序号
            Int32 i = DAL.FileDBTableManagerServer.GetTableIndex();
            String tableName = tablePrefix + i;
            FileStream writeFs = writeFsCache[tableName];
            //如果待写文件的大小超过了最大值，则创建一个新的待写文件流
            if (writeFs.Length + fileData.Length > FileService.fileMaxSize)
                writeFs = UpdateCacheWriteFs(tableName);
            //在大文件写入byte数组
            Int64 startIndex = -1;
            startIndex = writeFs.Position;
            writeFs.Write(fileData, 0, fileData.Length);
            if(alwaysFlush) writeFs.Flush();
            //把文件的索引数据插入到数据库表中
            Int32 dataLength = fileData.Length;
            fileData = null;
            //插入数据库，并获得主键值
            Int32 code = DAL.FileTable.InsertIntoDb(tableName,
                                        writeFs.Name.Substring(FileService.folderPath.Length),//传入的是相对路径
                                        fileDes, startIndex, dataLength);
            //拼凑成图片的序号
            return new FileIndex(i, code).FileUnicode;
        }

        #endregion

        #region "私有辅助方法"

        /// <summary>
        /// 获取读取文件的流对象
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        private static FileStream GetReadFileStream(String filePath)
        {
            if (readFileStreamCache.ContainsKey(filePath))
                return readFileStreamCache[filePath];
            else
            {
                FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                readFileStreamCache.Add(filePath, fs);
                return fs;
            }
        }

        /// <summary>
        /// 当大文件将近写满时,创建新的文件序号,并返回最新的文件序号
        /// </summary>
        /// <param name="tableName"></param>
        private static Int32 CreateNewFileIndex(String tableName)
        {
            Int32 newIndex = fileIndexCache[tableName] + 1;
            fileIndexCache[tableName] = newIndex;
            DAL.FileTableFileIndex.UpdateFileIndex(tableName, newIndex);
            return newIndex;
        }

        /// <summary>
        /// 当待写文件超过预设值的最大值时，更新待写文件流；并返回最新的文件流
        /// </summary>
        /// <returns></returns>
        private static FileStream UpdateCacheWriteFs(String tableName)
        {
            Int32 fileIndex = CreateNewFileIndex(tableName);
            FileStream fs = GetWriteFs(GetFilePath(FileService.folderPath, tableName, fileIndex));
            writeFsCache[tableName] = fs;
            return fs;
        }

        /// <summary>
        /// 根据文件名获取文件流
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        private static FileStream GetWriteFs(String filePath)
        {
            return new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
        }

        /// <summary>
        /// 根据表名和序号，获取完整的待写文件名
        /// 格式为 【日期天文件夹】\数据库表名_文件序号.dat
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        private static String GetFilePath(String folderName, String tableName, Int32 index)
        {
            String dateFolder = folderName + System.DateTime.Now.ToString("yyyy-MM-dd");
            dateFolder.EnsurePath();
            return dateFolder + "\\" + tableName + "_" + index + ".dat";
        }

        #endregion
    }
}
